package com.vader.common.utils;

import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.template.*;
import com.vader.common.entity.DO.ColumnInfoDO;
import com.vader.common.entity.DO.GeneratorConfigDO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 代码生成
 *
 * @author Vader
 * @date 2021/10/1
 **/
@Slf4j
@SuppressWarnings({"unchecked", "all"})
public class GeneratorUtil {

    public static final String SYS_TEM_DIR = System.getProperty("java.io.tmpdir") + File.separator;

    private static final String TIMESTAMP = "Timestamp";

    private static final String BIGDECIMAL = "BigDecimal";

    public static final String PK = "PRI";

    public static final String EXTRA = "auto_increment";

    /**
     * 获取后端代码模板
     *
     * @return templateNameList
     */
    private static List<String> listTemplateByAdmin() {
        List<String> templateNameList = new ArrayList<>();
        templateNameList.add("Entity");
        templateNameList.add("Dto");
        templateNameList.add("Mapper");
        templateNameList.add("Controller");
        templateNameList.add("Service");
        templateNameList.add("ServiceImpl");
        templateNameList.add("QueryCriteria");
        templateNameList.add("Repository");
        return templateNameList;
    }

    /**
     * 获取前端代码模板
     *
     * @return templateNameList
     */
    private static List<String> listTemplateByFront() {
        List<String> templateNameList = new ArrayList<>();
        templateNameList.add("api");
        templateNameList.add("index");
        return templateNameList;
    }

    public static List<Map<String, Object>> preview(List<ColumnInfoDO> columnInfoList, GeneratorConfigDO generatorConfigDO) {
        Map<String, Object> generatorMap = getGeneratorMap(columnInfoList, generatorConfigDO);
        List<Map<String, Object>> generatorList = new ArrayList<>();

        //获取后端模板
        List<String> templateList = listTemplateByAdmin();
        TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
        for (String templateName : templateList) {
            Map<String, Object> map = new HashMap<>(2);
            Template template = engine.getTemplate("generator/admin/" + templateName + ".ftl");
            map.put("content", template.render(generatorMap));
            map.put("name", templateName);
            generatorList.add(map);
        }

        //获取前端模板
        templateList = listTemplateByFront();
        for (String templateName : templateList) {
            Map<String, Object> map = new HashMap<>(1);
            Template template = engine.getTemplate("generator/front/" + templateName + ".ftl");
            map.put(templateName, template.render(generatorMap));
            map.put("content", template.render(generatorMap));
            map.put("name", templateName);
            generatorList.add(map);
        }
        return generatorList;
    }

    private static Map<String, Object> getGeneratorMap(List<ColumnInfoDO> columnInfoList, GeneratorConfigDO generatorConfigDO) {
        //存储模板字段数据
        Map<String, Object> generatorMap = new HashMap<>(16);
        //接口别名
        generatorMap.put("apiAlias", generatorConfigDO.getApiAlias());
        generatorMap.put("package", generatorConfigDO.getPack());
        generatorMap.put("moduleName", generatorConfigDO.getModuleName());
        generatorMap.put("authon", generatorConfigDO.getAuthor());
        generatorMap.put("date", LocalDate.now().toString());
        generatorMap.put("tableName", generatorConfigDO.getTableName());
        //大写开头的表名
        String upperTableName = StringUtils.toCapitalizeCamelCase(generatorConfigDO.getTableName());
        //小写开头的表名
        String tableName = StringUtils.toCamelCase(generatorConfigDO.getTableName());

        if (StringUtils.isNoneEmpty(generatorConfigDO.getPrefix())) {
            upperTableName = StringUtils.toCapitalizeCamelCase(StrUtil.removePrefix(generatorConfigDO.getTableName(), generatorConfigDO.getPrefix()));
            tableName = StringUtils.toCamelCase(StrUtil.removePrefix(generatorConfigDO.getTableName(), generatorConfigDO.getPrefix()));
            tableName = StringUtils.uncapitalize(tableName);
        }
        generatorMap.put("className", upperTableName);
        generatorMap.put("changeClassName", tableName);
        //是否存在时间戳
        generatorMap.put("hasTimestamp", false);
        //查询类中是否存在时间戳
        generatorMap.put("queryHasTimestamp", false);
        //是否存在bigDecimal类型
        generatorMap.put("hasBigDecimal", false);
        //查询类中是否存在bigDecimal类型
        generatorMap.put("queryHasBigDecimal", false);
        //是否需要创建查询
        generatorMap.put("hasQuery", false);
        //是否自增
        generatorMap.put("auto", false);
        //是否存在字典
        generatorMap.put("hasDict", false);
        //是否存在日期注解
        generatorMap.put("hasDateAnnotation", false);
        // 保存字段信息
        List<Map<String, Object>> columns = new ArrayList<>();
        // 保存查询字段的信息
        List<Map<String, Object>> queryColumns = new ArrayList<>();
        // 存储字典信息
        List<String> dicts = new ArrayList<>();
        // 存储 between 信息
        List<Map<String, Object>> betweens = new ArrayList<>();
        // 存储不为空的字段信息
        List<Map<String, Object>> isNotNullColumns = new ArrayList<>();

        for (ColumnInfoDO columnInfoDO : columnInfoList) {
            Map<String, Object> listMap = new HashMap<>(16);
            // 字段描述
            listMap.put("remark", columnInfoDO.getRemark());
            // 字段类型
            listMap.put("columnKey", columnInfoDO.getKeyType());
            // 主键类型
            String colType = ColumnUtil.cloToJava(columnInfoDO.getColumnType());
            // 小写开头的字段名
            String changeColumnName = StringUtils.toCamelCase(columnInfoDO.getColumnName());
            // 大写开头的字段名
            String capitalColumnName = StringUtils.toCapitalizeCamelCase(columnInfoDO.getColumnName());
            if (PK.equals(columnInfoDO.getKeyType())) {
                // 存储主键类型
                generatorMap.put("pkColumnType", colType);
                // 存储小写开头的字段名
                generatorMap.put("pkChangeColName", changeColumnName);
                // 存储大写开头的字段名
                generatorMap.put("pkCapitalColName", capitalColumnName);
            }
            // 是否存在 Timestamp 类型的字段
            if (TIMESTAMP.equals(colType)) {
                generatorMap.put("hasTimestamp", true);
            }
            // 是否存在 BigDecimal 类型的字段
            if (BIGDECIMAL.equals(colType)) {
                generatorMap.put("hasBigDecimal", true);
            }
            // 主键是否自增
            if (EXTRA.equals(columnInfoDO.getExtra())) {
                generatorMap.put("auto", true);
            }
            // 主键存在字典
            if (StringUtils.isNotBlank(columnInfoDO.getDictName())) {
                generatorMap.put("hasDict", true);
                dicts.add(columnInfoDO.getDictName());
            }

            // 存储字段类型
            listMap.put("columnType", colType);
            // 存储字原始段名称
            listMap.put("columnName", columnInfoDO.getColumnName());
            // 不为空
            listMap.put("istNotNull", columnInfoDO.getNotNull());
            // 字段列表显示
            listMap.put("columnShow", columnInfoDO.getListShow());
            // 表单显示
            listMap.put("formShow", columnInfoDO.getFormShow());
            // 表单组件类型
            listMap.put("formType", StringUtils.isNotBlank(columnInfoDO.getFormType()) ? columnInfoDO.getFormType() : "Input");
            // 小写开头的字段名称
            listMap.put("changeColumnName", changeColumnName);
            //大写开头的字段名称
            listMap.put("capitalColumnName", capitalColumnName);
            // 字典名称
            listMap.put("dictName", columnInfoDO.getDictName());
            // 日期注解
            listMap.put("dateAnnotation", columnInfoDO.getDateAnnotation());
            if (StringUtils.isNotBlank(columnInfoDO.getDateAnnotation())) {
                generatorMap.put("hasDateAnnotation", true);
            }
            // 添加非空字段信息
            if (columnInfoDO.getNotNull()) {
                isNotNullColumns.add(listMap);
            }
            // 判断是否有查询，如有则把查询的字段set进columnQuery
            if (!StringUtils.isBlank(columnInfoDO.getQueryType())) {
                // 查询类型
                listMap.put("queryType", columnInfoDO.getQueryType());
                // 是否存在查询
                generatorMap.put("hasQuery", true);
                if (TIMESTAMP.equals(colType)) {
                    // 查询中存储 Timestamp 类型
                    generatorMap.put("queryHasTimestamp", true);
                }
                if (BIGDECIMAL.equals(colType)) {
                    // 查询中存储 BigDecimal 类型
                    generatorMap.put("queryHasBigDecimal", true);
                }
                if ("between".equalsIgnoreCase(columnInfoDO.getQueryType())) {
                    betweens.add(listMap);
                } else {
                    // 添加到查询列表中
                    queryColumns.add(listMap);
                }
            }
            // 添加到字段列表中
            columns.add(listMap);
        }
        // 保存字段列表
        generatorMap.put("columns", columns);
        // 保存查询列表
        generatorMap.put("queryColumns", queryColumns);
        // 保存字段列表
        generatorMap.put("dicts", dicts);
        // 保存查询列表
        generatorMap.put("betweens", betweens);
        // 保存非空字段信息
        generatorMap.put("isNotNullColumns", isNotNullColumns);
        return generatorMap;
    }

    public static String download(List<ColumnInfoDO> columns, GeneratorConfigDO genConfig) throws IOException {
        // 拼接的路径：/tmpeladmin-gen-temp/，这个路径在Linux下需要root用户才有权限创建,非root用户会权限错误而失败，
        // 更改为： /tmp/eladmin-gen-temp/
        // String tempPath =SYS_TEM_DIR + "gen-temp" + File.separator + genConfig.getTableName() + File.separator;
        String tempPath = SYS_TEM_DIR + "gen-temp" + File.separator + genConfig.getTableName() + File.separator;
        Map<String, Object> genMap = getGeneratorMap(columns, genConfig);
        TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
        // 生成后端代码
        List<String> templates = listTemplateByAdmin();
        for (String templateName : templates) {
            Template template = engine.getTemplate("generator/admin/" + templateName + ".ftl");
            String filePath = getAdminFilePath(templateName, genConfig, genMap.get("className").toString(), tempPath + "eladmin" + File.separator);
            assert filePath != null;
            File file = new File(filePath);
            // 如果非覆盖生成
            if (!genConfig.getCover() && FileUtil.exist(file)) {
                continue;
            }
            // 生成代码
            genFile(file, template, genMap);
        }
        // 生成前端代码
        templates = listTemplateByFront();
        for (String templateName : templates) {
            Template template = engine.getTemplate("generator/front/" + templateName + ".ftl");
            String path = tempPath + "eladmin-web" + File.separator;
            String apiPath = path + "src" + File.separator + "api" + File.separator;
            String srcPath = path + "src" + File.separator + "views" + File.separator + genMap.get("changeClassName").toString() + File.separator;
            String filePath = getFrontFilePath(templateName, apiPath, srcPath, genMap.get("changeClassName").toString());
            assert filePath != null;
            File file = new File(filePath);
            // 如果非覆盖生成
            if (!genConfig.getCover() && FileUtil.exist(file)) {
                continue;
            }
            // 生成代码
            genFile(file, template, genMap);
        }
        return tempPath;
    }

    /**
     * 定义后端文件路径以及名称
     */
    private static String getAdminFilePath(String templateName, GeneratorConfigDO genConfig, String className, String rootPath) {
        String projectPath = rootPath + File.separator + genConfig.getModuleName();
        String packagePath = projectPath + File.separator + "src" + File.separator + "main" + File.separator + "java" + File.separator;
        if (!ObjectUtils.isEmpty(genConfig.getPack())) {
            packagePath += genConfig.getPack().replace(".", File.separator) + File.separator;
        }

        if ("Entity".equals(templateName)) {
            return packagePath + "domain" + File.separator + className + ".java";
        }

        if ("Controller".equals(templateName)) {
            return packagePath + "rest" + File.separator + className + "Controller.java";
        }

        if ("Service".equals(templateName)) {
            return packagePath + "service" + File.separator + className + "Service.java";
        }

        if ("ServiceImpl".equals(templateName)) {
            return packagePath + "service" + File.separator + "impl" + File.separator + className + "ServiceImpl.java";
        }

        if ("Dto".equals(templateName)) {
            return packagePath + "service" + File.separator + "dto" + File.separator + className + "Dto.java";
        }

        if ("QueryCriteria".equals(templateName)) {
            return packagePath + "service" + File.separator + "dto" + File.separator + className + "QueryCriteria.java";
        }

        if ("Mapper".equals(templateName)) {
            return packagePath + "service" + File.separator + "mapstruct" + File.separator + className + "Mapper.java";
        }

        if ("Repository".equals(templateName)) {
            return packagePath + "repository" + File.separator + className + "Repository.java";
        }

        return null;
    }

    /**
     * 定义前端文件路径以及名称
     */
    private static String getFrontFilePath(String templateName, String apiPath, String path, String apiName) {

        if ("api".equals(templateName)) {
            return apiPath + File.separator + apiName + ".js";
        }

        if ("index".equals(templateName)) {
            return path + File.separator + "index.vue";
        }

        return null;
    }

    private static void genFile(File file, Template template, Map<String, Object> map) throws IOException {
        // 生成目标文件
        Writer writer = null;
        try {
            FileUtil.touch(file);
            writer = new FileWriter(file);
            template.render(map, writer);
        } catch (TemplateException | IOException e) {
            throw new RuntimeException(e);
        } finally {
            assert writer != null;
            writer.close();
        }
    }

    public static void generatorCode(List<ColumnInfoDO> columns, GeneratorConfigDO generatorConfigDO) throws IOException {
        Map<String, Object> genMap = getGeneratorMap(columns, generatorConfigDO);
        TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
        // 生成后端代码
        List<String> templates = listTemplateByAdmin();
        for (String templateName : templates) {
            Template template = engine.getTemplate("generator/admin/" + templateName + ".ftl");
            String rootPath = System.getProperty("user.dir");
            String filePath = getAdminFilePath(templateName, generatorConfigDO, genMap.get("className").toString(), rootPath);

            assert filePath != null;
            File file = new File(filePath);

            // 如果非覆盖生成
            if (!generatorConfigDO.getCover() && FileUtil.exist(file)) {
                continue;
            }
            // 生成代码
            genFile(file, template, genMap);
        }

        // 生成前端代码
        templates = listTemplateByFront();
        for (String templateName : templates) {
            Template template = engine.getTemplate("generator/front/" + templateName + ".ftl");
            String filePath = getFrontFilePath(templateName, generatorConfigDO.getApiPath(), generatorConfigDO.getPath(), genMap.get("changeClassName").toString());

            assert filePath != null;
            File file = new File(filePath);

            // 如果非覆盖生成
            if (!generatorConfigDO.getCover() && FileUtil.exist(file)) {
                continue;
            }
            // 生成代码
            genFile(file, template, genMap);
        }
    }
}
